package org.fanjr.simplify.rest.utils;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.fanjr.simplify.rest.factory.SimplifyServerPostProcessor;
import org.fanjr.simplify.rest.model.SimplifyMappingModel;
import org.fanjr.simplify.rest.model.SimplifyServerModel;
import org.fanjr.simplify.rest.storage.SimplifyMappingStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo.BuilderConfiguration;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import org.springframework.web.util.UrlPathHelper;

/**
 * @author fanjr@vip.qq.com
 * @since 20190222 10:29:28
 */
public class SimplifyMvcMappingUtil {

	private static final Logger logger = LoggerFactory.getLogger(SimplifyServerPostProcessor.class);

	private static BuilderConfiguration config;

	private volatile static boolean init = false;

	private static final Map<String, SimplifyMappingModel> noMapping = new ConcurrentHashMap<>();

	private static final Map<String, SimplifyMappingModel> mapping = new ConcurrentHashMap<>();

	private static BeanFactory beanFactory;

	private static RequestMappingInfoHandlerMapping mappingHandler;

	private static ReadWriteLock lock = new ReentrantReadWriteLock();

	static {
		config = new BuilderConfiguration();
		config.setUrlPathHelper(new UrlPathHelper());
		config.setPathMatcher(new AntPathMatcher());
		config.setSuffixPatternMatch(true);
		config.setTrailingSlashMatch(false);
		config.setRegisteredSuffixPatternMatch(true);
		config.setContentNegotiationManager(new ContentNegotiationManager());
	}

	private SimplifyMvcMappingUtil() {
		// skip
	}

	public static void addMapping(SimplifyServerModel mappingModel) {
		String prefix = mappingModel.getPrifx();
		if (!prefix.startsWith("/")) {
			prefix = "/" + prefix;
		}
		if (!prefix.endsWith("/")) {
			prefix = prefix + "/";
		}
		Map<Method, String> map = SimplifyMappingStorage.INSTANCE.resolvPathByType(mappingModel.getBeanType(),
				mappingModel.getInter());
		for (Entry<Method, String> entry : map.entrySet()) {
			String path = prefix + entry.getValue();
			addMapping(new SimplifyMappingModel(path, mappingModel.getInstance(), entry.getKey()));
		}
	}

	public static void addMapping(SimplifyMappingModel mappingModel) {
		if (init) {
			doMapping(mappingModel);
		} else {
			try {
				if (lock.readLock().tryLock()) {
					if (init) {
						doMapping(mappingModel);
					} else {
						noMapping(mappingModel);
					}
				} else {
					lock.readLock().lock();
					doMapping(mappingModel);
				}
			} finally {
				lock.readLock().unlock();
			}
		}
	}

	public static synchronized void setBeanFactory(BeanFactory beanFactory) {
		SimplifyMvcMappingUtil.beanFactory = beanFactory;
	}

	private static void doMapping(SimplifyMappingModel mappingModel) {
		mapping.put(mappingModel.path, mappingModel);
		RequestMappingInfo.Builder builder = RequestMappingInfo.paths(mappingModel.path);
		mappingHandler.registerMapping(builder.options(config).build(), mappingModel.handler, mappingModel.method);
		logger.info("Register HTTP mapping [{}]->[{}]", mappingModel.path, mappingModel.method);
	}

	private static void noMapping(SimplifyMappingModel mappingModel) {
		noMapping.put(mappingModel.path, mappingModel);
	}

	public static synchronized void init() {
		if (!init) {
			try {
				lock.writeLock().lock();
				mappingHandler = beanFactory.getBean(RequestMappingInfoHandlerMapping.class);
				for (SimplifyMappingModel model : noMapping.values()) {
					doMapping(model);
				}
				noMapping.clear();
			} finally {
				lock.writeLock().unlock();
			}
			init = true;
		}
	}

}
